Profesor: Anthony D. Cho
Ayudante: Esnil Guevara
Asunto: Clustering de canciones de Spotify
Evaluación: Tarea 02
Estudiantes:
Aplicar algunas de las técnicas aprendidas de Clustering para crear listas de reproducción en Spotify
Se va a considerar en la evaluación:
import pandas as pd
import numpy as np
from tqdm import tqdm
from itertools import product
## Agregar las librerias que iran a usar
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from pca import pca
#Clustering
from sklearn.cluster import KMeans
from fcmeans import FCM
from sklearn.mixture import GaussianMixture
from sklearn.cluster import DBSCAN
from sklearn.neighbors import NearestNeighbors
#Outliers
from sklearn.neighbors import LocalOutlierFactor
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
#Metrics
from sklearn.metrics import silhouette_score, davies_bouldin_score
#Librerias para trabajar texto
from string import punctuation
from unidecode import unidecode
# Mute all future warnings
import warnings
warnings.filterwarnings("ignore")
dataset = pd.read_csv("musics.csv")
dataset
| artist_name | artist_id | album_id | album_type | album_release_date | album_release_year | album_release_date_precision | danceability | energy | key | ... | track_name | track_preview_url | track_number | type | track_uri | external_urls.spotify | album_name | key_name | mode_name | key_mode | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2Pac | 1ZwdS5xdxEREPySFridCfh | 1nGbXgS6toEOcFCDwEl5R3 | album | 2019-08-01 | 2019.0 | day | 0.656 | 0.882 | 0 | ... | California Love | https://p.scdn.co/mp3-preview/93e456ef0b73f23f... | 1 | track | spotify:track:6ayeqYtOtwVhqVB6k6MKoh | https://open.spotify.com/track/6ayeqYtOtwVhqVB... | California Love | C | major | C major |
| 1 | 2Pac | 1ZwdS5xdxEREPySFridCfh | 1nGbXgS6toEOcFCDwEl5R3 | album | 2019-08-01 | 2019.0 | day | 0.810 | 0.642 | 8 | ... | Slippin' Into Darkness | https://p.scdn.co/mp3-preview/440595604d3f4946... | 2 | track | spotify:track:1UDsnzBp8gUCFsrzUDlZI9 | https://open.spotify.com/track/1UDsnzBp8gUCFsr... | California Love | G# | major | G# major |
| 2 | 2Pac | 1ZwdS5xdxEREPySFridCfh | 1nGbXgS6toEOcFCDwEl5R3 | album | 2019-08-01 | 2019.0 | day | 0.548 | 0.590 | 4 | ... | Ride or Die | https://p.scdn.co/mp3-preview/cc18dc90d609d375... | 3 | track | spotify:track:3bKs15o7F9VP6GBExCbb6H | https://open.spotify.com/track/3bKs15o7F9VP6GB... | California Love | E | minor | E minor |
| 3 | 2Pac | 1ZwdS5xdxEREPySFridCfh | 1nGbXgS6toEOcFCDwEl5R3 | album | 2019-08-01 | 2019.0 | day | 0.839 | 0.657 | 5 | ... | I Ain't Mad At Cha | https://p.scdn.co/mp3-preview/d138f0170423cd9a... | 4 | track | spotify:track:4L0iAst3yLonw8aGxTRCvb | https://open.spotify.com/track/4L0iAst3yLonw8a... | California Love | F | minor | F minor |
| 4 | 2Pac | 1ZwdS5xdxEREPySFridCfh | 1nGbXgS6toEOcFCDwEl5R3 | album | 2019-08-01 | 2019.0 | day | 0.854 | 0.694 | 0 | ... | Static II | https://p.scdn.co/mp3-preview/dddb7d0ea0205338... | 5 | track | spotify:track:66men3J5qFERvIY06M5hQ9 | https://open.spotify.com/track/66men3J5qFERvIY... | California Love | C | minor | C minor |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 447617 | ZZ Top | 2AM4ilv6UzW0uMRuqKtDgN | 0Y9jM9umdMOH7t19urnOw4 | album | 1970-01-16 | 1970.0 | day | 0.673 | 0.520 | 0 | ... | Neighbor, Neighbor | https://p.scdn.co/mp3-preview/530e4ce805075d4e... | 6 | track | spotify:track:4snfGOJDRVfY9jH43CsmJB | https://open.spotify.com/track/4snfGOJDRVfY9jH... | ZZ Top's First Album | C | major | C major |
| 447618 | ZZ Top | 2AM4ilv6UzW0uMRuqKtDgN | 0Y9jM9umdMOH7t19urnOw4 | album | 1970-01-16 | 1970.0 | day | 0.674 | 0.397 | 4 | ... | Certified Blues | https://p.scdn.co/mp3-preview/ee79a42629975bd9... | 7 | track | spotify:track:7mdPXhJBfxJBonEmqZvm9t | https://open.spotify.com/track/7mdPXhJBfxJBonE... | ZZ Top's First Album | E | minor | E minor |
| 447619 | ZZ Top | 2AM4ilv6UzW0uMRuqKtDgN | 0Y9jM9umdMOH7t19urnOw4 | album | 1970-01-16 | 1970.0 | day | 0.466 | 0.406 | 7 | ... | Bedroom Thang | https://p.scdn.co/mp3-preview/dd0df120d0b10f80... | 8 | track | spotify:track:23dC4zCpB1bnLzBxAXDLD7 | https://open.spotify.com/track/23dC4zCpB1bnLzB... | ZZ Top's First Album | G | major | G major |
| 447620 | ZZ Top | 2AM4ilv6UzW0uMRuqKtDgN | 0Y9jM9umdMOH7t19urnOw4 | album | 1970-01-16 | 1970.0 | day | 0.611 | 0.337 | 0 | ... | Just Got Back from Baby's | https://p.scdn.co/mp3-preview/5fa536adf7c6117e... | 9 | track | spotify:track:7AX7qVqFljHOJC3FIQ8a6t | https://open.spotify.com/track/7AX7qVqFljHOJC3... | ZZ Top's First Album | C | major | C major |
| 447621 | ZZ Top | 2AM4ilv6UzW0uMRuqKtDgN | 0Y9jM9umdMOH7t19urnOw4 | album | 1970-01-16 | 1970.0 | day | 0.635 | 0.645 | 4 | ... | Backdoor Love Affair | https://p.scdn.co/mp3-preview/311f47b27f09ff6e... | 10 | track | spotify:track:7w83TSpDeFsL8dV9pAHWn6 | https://open.spotify.com/track/7w83TSpDeFsL8dV... | ZZ Top's First Album | E | minor | E minor |
447622 rows × 36 columns
dataset.shape
(447622, 36)
dataset.columns
Index(['artist_name', 'artist_id', 'album_id', 'album_type',
'album_release_date', 'album_release_year',
'album_release_date_precision', 'danceability', 'energy', 'key',
'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness',
'liveness', 'valence', 'tempo', 'track_id', 'analysis_url',
'time_signature', 'disc_number', 'duration_ms', 'explicit',
'track_href', 'is_local', 'track_name', 'track_preview_url',
'track_number', 'type', 'track_uri', 'external_urls.spotify',
'album_name', 'key_name', 'mode_name', 'key_mode'],
dtype='object')
dataset.loc[191428]
artist_name Linkin Park artist_id 6XyY86QOPPrYVGvF9ch6wz album_id 6PFPjumGRpZnBzqnDci6qJ album_type album album_release_date 2000-10-24 album_release_year 2000.0 album_release_date_precision day danceability 0.556 energy 0.864 key 3 loudness -5.87 mode 0 speechiness 0.0584 acousticness 0.00958 instrumentalness 0.0 liveness 0.209 valence 0.4 tempo 105.143 track_id 7q115ia4fQn9zonjpexWsY analysis_url https://api.spotify.com/v1/audio-analysis/7q11... time_signature 4 disc_number 1 duration_ms 216880 explicit False track_href https://api.spotify.com/v1/tracks/7q115ia4fQn9... is_local False track_name In the End track_preview_url https://p.scdn.co/mp3-preview/6ce8bcf317e8c562... track_number 8 type track track_uri spotify:track:7q115ia4fQn9zonjpexWsY external_urls.spotify https://open.spotify.com/track/7q115ia4fQn9zon... album_name Hybrid Theory key_name D# mode_name minor key_mode D# minor Name: 191428, dtype: object
La data contiene 447662 canciones de spotify, incluyendo sus detalles (artista, álbum, track), ids internos de spotify así como variables numéricas, que representan atributos (features) musicales de esta.
Por ejemplo:
(Mas info: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features)
## Seleccion de algunas columnas (features)
col_features = ['acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness',
'loudness','speechiness','valence','tempo', 'key',
'mode', 'time_signature', 'duration_ms']
## Mostrar algunas estadisticas
dataset[col_features].describe()
| acousticness | danceability | energy | instrumentalness | liveness | loudness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 447622.000000 | 4.476220e+05 |
| mean | 0.698733 | 0.391103 | 0.340512 | 0.506068 | 0.221720 | -18.671858 | 0.068922 | 0.337392 | 108.736640 | 5.061246 | 0.683420 | 3.727714 | 2.291097e+05 |
| std | 0.369360 | 0.183373 | 0.317557 | 0.413743 | 0.219318 | 8.640242 | 0.094167 | 0.274319 | 31.650459 | 3.491417 | 0.465142 | 0.717955 | 1.766587e+05 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | -60.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.066000e+03 |
| 25% | 0.394000 | 0.252000 | 0.075600 | 0.001690 | 0.096800 | -24.445000 | 0.037800 | 0.089400 | 82.391000 | 2.000000 | 0.000000 | 4.000000 | 1.234400e+05 |
| 50% | 0.923000 | 0.370000 | 0.210000 | 0.715000 | 0.123000 | -19.476500 | 0.044300 | 0.274000 | 105.723500 | 5.000000 | 1.000000 | 4.000000 | 1.949610e+05 |
| 75% | 0.986000 | 0.514000 | 0.582000 | 0.901000 | 0.253000 | -11.644000 | 0.058400 | 0.537000 | 131.051000 | 8.000000 | 1.000000 | 4.000000 | 2.715600e+05 |
| max | 0.996000 | 0.986000 | 1.000000 | 1.000000 | 1.000000 | 0.496000 | 0.971000 | 0.996000 | 244.952000 | 11.000000 | 1.000000 | 5.000000 | 4.796395e+06 |
Decida que columnas de los datos usará para su clustering. Se sugiere usar las columnas numéricas (dataset[col_features]), pero puede incluir o sacar otras columns si lo desea que no sean consideradas pertinentes para clusterizar (por ejemplo, el año de la canción, o su duración).
Decida qué filas (datos) utilizará. Puede pre-seleccionar un subconjunto de datos para el trabajo. Por ejemplo, canciones de los 90's, o canciones no-acústicas.
Preprocese los datos. Por ejemplo, recuerde que ciertos algoritmos necesitan que los datos estén en la misma escala, y esto no necesariamente se cumple. Podrían haber datos no válidos o outliers que quisiera sacar.
Requisito: Describa en la ventana siguiente las decisiones que tomó al respecto, indicando las razones para tomar esta decisión. En las ventanas siguientes, ponga los códigos utilizados (sólo los necesarios) para poder reproducir el resultado final.
Respuesta:
Inicialmente usaremos las columnas recomendadas col_feartures
Usaremos solamente canciones donde sus albums fueron lanzados el año 2000 con el objetivo de minimizar los recursos computacionales requeridos.
Se hizo escalamiento de datos y se limpiaron:
loudness sobre energy, las cuales tienen una correlacion por sobre 0.85.De esta manera nuestro subconjunto de datos pasa de tener 4103 registros y 13 features a 2879 resgirtos y 12 features.
#Subconjunto canciones del año 2000
dataset_sub = dataset[dataset['album_release_year']==2000]
dataset_sub.shape
(4103, 36)
Seleccionamos canciones del año 2000
#Limpieza de duplicados
#Primero debemos trabajar los titulos de las canciones para poder identificarlos lo mejor posible.
#Limpiamos espacios extras
dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].apply(lambda x: x.str.strip())
#Limpiamos caracteres especiales y puntuaciones
def strip_words(x):
x = str(x)
non_words = list(punctuation)
sentece = ''.join([letra for letra in x if letra not in non_words]) #Quitamos carateres y puntuaciones raras
new_sentence = unidecode(sentece)
return new_sentence
dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].applymap(strip_words)
#Dejamos todo en minusculas
dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].apply(lambda x: x.str.title())
#Chequeamos duplicados
print(f"Nº de canciones duplicadas: {dataset_sub.duplicated(subset=['artist_id','track_name']).sum()}")
#Existen canciones duplicadas. Dicho esto trataremos de dejar solamente las que no son en vivo (para un mejor sonido) y y las mas recientes.
dataset_sub = dataset_sub.sort_values(by=['artist_name','track_name','liveness','album_release_year'], ascending=[True,True,True,False])
#Luego de ordenar la data, nos aseguramos de quedarnos con el primer registro.
dataset_sub = dataset_sub.drop_duplicates(subset=['artist_name','track_name'],keep='first')
Nº de canciones duplicadas: 959
Despues del tratamiento que hicimos, pudimos detectar 959 canciones duplicadas. Tratamos de quedarnos con las que no eran en vivo y con fecha de lanzamiento mas reciente.
#Selección de features recomendadas
data = dataset_sub[col_features]
#Adicionalmente se obtienen las columnas artist_name, album_name, track_name y track_id para poder crear nuestro playlist mas adelante
data_info = dataset_sub[['artist_name','album_name','track_name','track_id']]
print(data.shape,data_info.shape)
(3144, 13) (3144, 4)
Nuestras variables independientes inciales estaran sujetas a las columnas recomendadas col_features. Adicionalmente extramos informacion necesaria para crear nuestras playlist mas adelante y las guardaremos en la variable data_info.
#Chequeamos si existen datos faltantes
print(data.isna().sum(), data_info.isna().sum())
#No existen datos faltantes
acousticness 0 danceability 0 energy 0 instrumentalness 0 liveness 0 loudness 0 speechiness 0 valence 0 tempo 0 key 0 mode 0 time_signature 0 duration_ms 0 dtype: int64 artist_name 0 album_name 0 track_name 0 track_id 0 dtype: int64
No tenemos datos faltantes.
#Observamos si existe correlacion entre variables. Si existe alguna con una correlacion >0.75, tendremos que dropear una de las 2.
plt.figure(figsize=(15,10))
sns.heatmap(data=data.corr(), annot=True, fmt=".2f", cmap="YlGnBu")
plt.show()
La variable loudness y energy tienen una alta correlacion, por lo tanto no aportaran mucho al modelo si dejamos ambas. Dicho esto dropearemos loudness.
Adicionalmente se pueden observar otras correlaciones entre variables que las podres observar con mas detalle en el biplot.
#La variable loudness y energy tienen una alta correlacion. Dropearemos loudness.
data = data.drop('loudness', axis=1)
data.head()
| acousticness | danceability | energy | instrumentalness | liveness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 387 | 0.608 | 0.506 | 0.413 | 0.000000 | 0.2140 | 0.2630 | 0.581 | 98.720 | 6 | 1 | 4 | 140373 |
| 384 | 0.177 | 0.718 | 0.666 | 0.000003 | 0.2150 | 0.3610 | 0.643 | 120.544 | 11 | 1 | 5 | 182440 |
| 385 | 0.183 | 0.612 | 0.704 | 0.000000 | 0.1130 | 0.5600 | 0.140 | 82.276 | 8 | 0 | 4 | 294400 |
| 379 | 0.126 | 0.603 | 0.733 | 0.000008 | 0.0766 | 0.4270 | 0.489 | 82.902 | 8 | 1 | 4 | 176866 |
| 399 | 0.433 | 0.758 | 0.542 | 0.000000 | 0.0857 | 0.0502 | 0.591 | 93.377 | 1 | 0 | 4 | 199800 |
#Dada la diferencia de magnitud de los datos, se debe normalizar los datos, de lo contrario nuestros modelos no nos daran buenos resultados.
scaler = StandardScaler()
data_scaled = pd.DataFrame(scaler.fit_transform(data),columns=data.columns, index=data.index)
data_scaled
| acousticness | danceability | energy | instrumentalness | liveness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 387 | 0.263767 | 0.265679 | -0.223575 | -0.711196 | -0.055430 | 2.223782 | 0.625767 | -0.479719 | 0.183876 | 0.738730 | 0.304075 | -0.660752 |
| 384 | -0.823009 | 1.355166 | 0.551399 | -0.711187 | -0.050841 | 3.349758 | 0.848299 | 0.222255 | 1.660895 | 0.738730 | 2.167648 | -0.469731 |
| 385 | -0.807880 | 0.810422 | 0.667798 | -0.711196 | -0.518883 | 5.636179 | -0.957086 | -1.008645 | 0.774683 | -1.353674 | 0.304075 | 0.038664 |
| 379 | -0.951607 | 0.764171 | 0.756629 | -0.711175 | -0.685909 | 4.108069 | 0.295557 | -0.988509 | 0.774683 | 0.738730 | 0.304075 | -0.495042 |
| 399 | -0.177500 | 1.560729 | 0.171570 | -0.711196 | -0.644152 | -0.221194 | 0.661659 | -0.651578 | -1.293143 | -1.353674 | 0.304075 | -0.390902 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 445419 | -0.870918 | 1.545312 | 0.533020 | -0.711000 | -0.482173 | -0.180980 | 0.758568 | -0.597766 | -0.111528 | 0.738730 | 0.304075 | 0.154787 |
| 445406 | -1.048434 | 0.507216 | 0.710682 | -0.711192 | 1.032077 | 3.372737 | 0.909316 | 1.307028 | -1.293143 | 0.738730 | 0.304075 | 0.499407 |
| 445385 | -1.243599 | 0.954317 | 1.194658 | -0.711186 | 0.541093 | 2.591448 | 1.034939 | 1.960754 | 0.479279 | 0.738730 | 0.304075 | -0.261278 |
| 445396 | 1.000051 | 0.460964 | -0.701424 | -0.711196 | 0.463086 | 3.981683 | 0.719087 | -0.739196 | -1.293143 | 0.738730 | 0.304075 | -0.831548 |
| 445402 | -1.175014 | 1.447669 | 0.147065 | -0.673413 | -0.014132 | -0.102851 | 0.378109 | -0.824434 | 0.479279 | 0.738730 | 0.304075 | -0.180814 |
3144 rows × 12 columns
#Veremos si existen outliers (Al menos usando boxplots).
data_scaled.boxplot()
plt.xticks(rotation=45,ha='right')
plt.title('data sin reduccion de outliers')
plt.show()
#Usando el metodo de 3 desvaciones estandar. Veremos cuantas filas contienen a lo menos 1 outlier.
outliers = (data_scaled>3 )|(data_scaled<-3)
print(f"Porcentaje de filas con outliers con metodo de 3 desvaciones estandar: {outliers.any(axis=1).mean():.2%}")
#Graficando
model = PCA(n_components=2).fit(data_scaled)
x_pca = model.transform(data_scaled)
sns.scatterplot(x = x_pca[:,0], y = x_pca[:,1], hue = outliers.any(axis=1), palette = ['#33658a','#fe4a49'])
plt.title('outliers con 3 desviaciones estandar')
plt.show()
Porcentaje de filas con outliers con metodo de 3 desvaciones estandar: 8.43%
# Ahora este ultimo metodo no seria ten eficiente si no tenemos distribuciones normales,lo que justamente aqui no pasa para todas las variables.
plt.rcdefaults()
data_scaled.hist(grid=False)
plt.tight_layout()
plt.show()
Acorde a lo anterior tenemos que 8.43% de las filas tienen al menos 1 outlier, pero que este modelo trabaja bien sobre distribuciones normales, lo cual no es 100% cierto para todas las variables en nuestro dataset. Ademas, sabemos que este metodo no toma en cuenta la interacción entre variables. Dicho esto usaremos SVM para detectar outliers, usando como proxy de contaminacion el 8.43% del modelo anterior.
# Acorde a lo anterior tenemos que 8.43% de las filas tienen al menos 1 outlier, pero sabemos que este metodo no toma en cuenta la interacción entre variables.
# Dicho esto usaremos SVM para detectar outliers, usando como proxy de contaminacion el 8.43% del modelo anterior.
outliers = OneClassSVM(nu=0.0843, kernel = 'rbf', gamma = 'auto',max_iter=-1).fit_predict(data_scaled)
print(f"Porcentaje de filas con outliers con medelo SVM - kernel rbf: {(outliers==-1).sum()/data_scaled.shape[0]:.2%}")
#Graficando
model = PCA(n_components=2).fit(data_scaled)
x_pca = model.transform(data_scaled)
sns.scatterplot(x = x_pca[:,0], y = x_pca[:,1], hue = [True if i == -1 else False for i in outliers], palette = ['#33658a','#fe4a49'])
plt.title('outliers con SVM - kernel rbf')
plt.show()
Porcentaje de filas con outliers con medelo SVM - kernel rbf: 8.49%
Finalmente nos filtramos los outliers bajo el modelo SVM
#Con la intencion de mantener consitencia entre ambos datasets, se eliminan los outliers de ambos. Recordemos que vamos a necesitar la info para crear nuestra playlist.
no_outliers = [False if i == -1 else True for i in outliers]
# Nos quedamos con nuestras filas sin outliers para ambos datasets
data_scaled = data_scaled[no_outliers]
data_info = data_info[no_outliers]
#Visualizamos con reduccion de outliers
data_scaled.boxplot()
plt.xticks(rotation=45,ha='right')
plt.title('data con reduccion de outliers')
plt.show()
Con la intención de mantener la mayoria de nuestros datos usamos el modelo SVM. De este modo solo eliminamos el ~8.4% de las filas totales.
data_scaled.shape
(2877, 12)
Nos quedamos con un data set de casi 2900 filas y 12 variables independientes.
Requisito: Describa en la ventana siguiente las técnicas que probó, así como el modelo seleccionado (con sus valores de hiperparámetros finales). En las siguientes ventanas, ponga los códigos utilizados para poder reproducir el resultado final. (Ojo: no necesita poner todos los códigos de todos los métodos que probó. Basta con aquellos relacionados con el modelo final seleccionado).
Respuesta:
Se usaron distintos metodos a modo de obtener un modelo que nos diera un mejor resultado, buscando el punto de quiebre mas pronunciado usando la metodologia del codo, asi como el score mas alto para silueta y el mas bajo para davis boulding.
De esta manera, se presenta a continuacion una tabla con los resultados para cada metodo y su puntuacion respectiva
Mejor Nº de cluster segun score:
| Modelo | Metodo Codo | Silueta | Boulding |
|---|---|---|---|
| K-Means | 2 | 2 | 2 |
| C-Means | 2 | 2 | 2 |
| GMM | 2 | 2 | 2 |
Ahora bien, se decide continuar con K-Means para este estudio, ya que presenta unscore de silueta y boulding levemente mejor que el resto. Recordemos que un score silueta mas alto y un boulding mas bajo es mejor.
| Modelo | n_clusters | silueta | davies_bouldin |
|---|---|---|---|
| K-Means | 2 | 0.250665 | 1.551570 |
| C-Means | 2 | 0.247316 | 1.580740 |
| GMM-diag | 2 | 0.238371 | 1.587731 |
El modelo final consiste en los siguientes hiperparametros:
KMeans( n_clusters=2,n_init='auto', init='k-means++', random_state=0, max_iter=300, tol=0.0001)
#Buscando el mejor modelo
#Se recomienda usar max cluser = numero de atributos + 1
max_n_clusters = data_scaled.shape[1]+1
## Almacenado de errores al cuadrado
sse = []
for k in range(1, max_n_clusters):
## Instancia del modelo
kmeans_model = KMeans(n_clusters=k,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)
## Ajuste del modelo
kmeans_model.fit(data_scaled)
## Guardar el SSE obtenido
sse.append(kmeans_model.inertia_)
plt.figure(figsize=(10, 5))
plt.plot(range(1, max_n_clusters), sse)
plt.title('Elbow curve')
plt.xlabel('Numero de cluster')
plt.ylabel('SSE')
plt.tight_layout()
plt.show()
max_n_clusters = data_scaled.shape[1]+1
## Almacenado de SSE-score
sse = []
silueta_km_lst = []
davies_bouldin_km_lst = []
n_cluster_lst = []
for k in range(2, max_n_clusters):
## Instancia del modelo
kmeans_model = KMeans(n_clusters=k,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)
## Ajuste del modelo
kmeans_model.fit(data_scaled)
# Cálculo de métricas para K-Means
silueta_km = silhouette_score(data_scaled, kmeans_model.labels_)
davies_bouldin_km = davies_bouldin_score(data_scaled, kmeans_model.labels_)
silueta_km_lst.append(silueta_km)
davies_bouldin_km_lst.append(davies_bouldin_km)
n_cluster_lst.append(k)
silueta_davis = pd.DataFrame()
silueta_davis['n_clusters'] = n_cluster_lst
silueta_davis['silueta']=silueta_km_lst
silueta_davis['davies_bouldin']=davies_bouldin_km_lst
print(silueta_davis)
fig, ax = plt.subplots(figsize=(10, 5))
y2 = ax.twinx()
sns.lineplot(data=silueta_davis, x='n_clusters', y='silueta', ax=ax,label='Silueta', color='blue')
sns.lineplot(data=silueta_davis, x='n_clusters', y='davies_bouldin', ax=y2, label='Davies Bouldin', color='red')
# Add labels at the end of each line
silueta_max = last_row_silueta = silueta_davis.iloc[-1]['silueta']
davies_max = last_row_silueta = silueta_davis.iloc[-1]['davies_bouldin']
ax.text(silueta_davis['n_clusters'].iloc[-1], silueta_max, 'Silueta', color='blue', va='center')
y2.text(silueta_davis['n_clusters'].iloc[-1], davies_max, 'Davies Bouldin', color='red', va='center')
# Set labels for both x-axes
ax.set_xlabel('Number of Clusters')
y2.set_xlabel('Number of Clusters')
ax.legend_.remove()
y2.legend_.remove()
plt.xlim(1, 14)
plt.title('Score de silueta y davies_bouldin')
plt.show()
n_clusters silueta davies_bouldin 0 2 0.250665 1.551570 1 3 0.183340 2.027633 2 4 0.152244 2.120622 3 5 0.161261 1.949095 4 6 0.162803 1.894481 5 7 0.145194 1.966472 6 8 0.155780 1.949053 7 9 0.134181 1.877869 8 10 0.135119 1.801033 9 11 0.138361 1.769663 10 12 0.133374 1.740106
Guiandonos por la metodologia del codo y complementando con las metricas de silueta y davis bouldin, obtenemos que el numero cluster optimos en 2.
#Modelo con mejor parametro
kmeans_model = KMeans(n_clusters=2,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)
## Ajuste del modelo con los datos
kmeans_model.fit(data_scaled)
## Transformación de los datos usando PCA
pca_model = PCA(n_components=2, random_state=0)
pca_model.fit(data_scaled)
pca_data = pca_model.transform(data_scaled)
## Crear un dataframe con los datos transformados mediante PCA y agregar las etiquetas de los clusteres
pca_data = pd.DataFrame(pca_data, columns=['PC1', 'PC2'])
pca_data['cluster'] = kmeans_model.predict(data_scaled)
## Graficar los datos por clases.
N = pca_data['cluster'].nunique()
plt.figure(figsize=(8, 6))
sns.scatterplot(data=pca_data,
x='PC1', y='PC2',
hue='cluster',
palette=sns.color_palette()[:N])
plt.legend(loc=[1.01, 0.5], title='Cluster')
plt.title('Cluster formados')
plt.show()
Requisito: Describa en la ventana siguiente los análisis que hizo, así como la conclusión a la que llegó. Incluya en la ventana siguiente los códigos finales utilizados para llegar a esta conclusión.
Respuesta:
A modo de describir nuestros 2 cluster formados anteriormente, usamos analisis 2D. Luego finalizamos usando boxplot y analisis de medias para describir mas en detalle cada grupo.
Clúster 0: Las canciones en este clúster tienen una "acousticness" relativamente baja, lo que significa que es poco probable que tengan elementos acústicos, pero si mas electrónicos. La "danceability" es razonablemente alta, lo que sugiere que son adecuadas para bailar. Tienen una alta "energy", lo que indica un alto nivel de energía y ritmo. La "instrumentalness" es baja, lo que sugiere que estas canciones probablemente contienen voces. La "liveness" es moderada-baja, lo que indica que la mayoria de las canciones son grabaciones de estudio en lugar de actuaciones en vivo. La "speechiness" es baja, lo que apunta que probablemente es muscia y no un talk show, audio book, etc. La "valence" es relativamente alta, lo que sugiere un tono alegre. El "tempo" es rápido, con un valor de 121.73 BPM. Las canciones en este clúster están en modo mayor. La "duration_ms" es moderada en términos de duración.
Clúster 1: En contraste, las canciones en este clúster tienen una alta "acousticness", lo que sugiere que son predominantemente acústicas. La "danceability" es mas baja, lo que indica que no son muy adecuadas para el baile. Tienen una baja "energy", lo que sugiere un nivel de energía y ritmo más bajo. La "instrumentalness" es alta, lo que sugiere que estas canciones tienen menos probabilidad de contener voces. La "liveness" es baja, lo que sugiere que son grabaciones de estudio. La "speechiness" es baja, lo que indica que probablemente es muscia y no un talk show, audio book, etc. La "valence" es mas baja, lo que sugiere un tono un poco mas melancolico. El "tempo" es más lento, con un valor de 103.32 BPM. Estas canciones están en modo mayor aunque levemente mas bajo que el cluster 0. La "duration_ms" es más larga en comparación con el clúster 0. Finalmente su time_signature tiene una variación importante si se le compara con el otro cluster.
En resumen, el clúster 0 parece estar compuesto por canciones más enérgicas y adecuadas para bailar, mientras que el clúster 1 está formado por canciones predominantemente acústicas con un ritmo más lento y una energía más relajada, pero con un tono mas melancolico. Ambos clústeres tienen características similares en términos de key, mode y speechiness, pero difieren en las demás métricas.
#Varianza explicada por cantidad de PCA
pca_model = PCA()
pca_model.fit_transform(data_scaled)
pca_var = pd.DataFrame([pca_model.explained_variance_ratio_,
pca_model.explained_variance_ratio_.cumsum()]).T \
.rename(columns={0:'variance',1:'cumulative_variance'})
pca_var['N_PCA'] = [i+1 for i in range (len(pca_var.variance))]
pca_var
| variance | cumulative_variance | N_PCA | |
|---|---|---|---|
| 0 | 0.349398 | 0.349398 | 1 |
| 1 | 0.105933 | 0.455331 | 2 |
| 2 | 0.096997 | 0.552328 | 3 |
| 3 | 0.090348 | 0.642676 | 4 |
| 4 | 0.081498 | 0.724174 | 5 |
| 5 | 0.069014 | 0.793188 | 6 |
| 6 | 0.056463 | 0.849651 | 7 |
| 7 | 0.044237 | 0.893888 | 8 |
| 8 | 0.037231 | 0.931119 | 9 |
| 9 | 0.033441 | 0.964560 | 10 |
| 10 | 0.024764 | 0.989324 | 11 |
| 11 | 0.010676 | 1.000000 | 12 |
2 PCA explican 45.9% de los datos.
#Para poder visualizar los datos en un espacio 2D, usaremos las primeras 2 componentes.
model = pca(verbose=False,n_components=2,random_state=0)
#En la siguiente tabla se observa que features contribuyen mas a la varianza de las primeras 2 componentes.
model.fit_transform(data_scaled)['topfeat']
| PC | feature | loading | type | |
|---|---|---|---|---|
| 0 | PC1 | energy | -0.469967 | best |
| 1 | PC2 | mode | -0.766313 | best |
| 2 | PC1 | acousticness | 0.447399 | weak |
| 3 | PC1 | danceability | -0.393230 | weak |
| 4 | PC1 | instrumentalness | 0.364452 | weak |
| 5 | PC1 | liveness | -0.162428 | weak |
| 6 | PC1 | speechiness | -0.106605 | weak |
| 7 | PC1 | valence | -0.420971 | weak |
| 8 | PC1 | tempo | -0.195038 | weak |
| 9 | PC2 | key | 0.621789 | weak |
| 10 | PC1 | time_signature | -0.103514 | weak |
| 11 | PC1 | duration_ms | 0.158561 | weak |
Las variables que influyen mas en en PC1, PC2 son energy y mode respectivamente.
#Ahora iremos harmando nuestro biplot, para ver que features y que magnitud corresponde a cada cluster.
#Primero nuestro scatterplot con la etiquetas respectivas para nuestras PC1 y PC2
model.scatter(labels=kmeans_model.labels_, legend=True,figsize=(15,8))
plt.show()
[scatterd] >INFO> Create scatterplot
# 2D plot with samples colored on color_intensity
model.biplot(labels=kmeans_model.labels_, legend=True, cmap=None,figsize=(15,8))
plt.show()
[scatterd] >INFO> Create scatterplot
Aqui podemos observar nuevamente la correlacion entre variables en un espacio de 2 dimensiones. Por ejemplo, energy, danceability y valence estan correlacionadas entre si ya que se mueven en la misma direccion y son muy cercanas en el plano. Lo mismo podemos decir de instrumentalness y acousticness.
# 2D plot with samples colored on color_intensity
model.biplot(labels=kmeans_model.labels_, legend=True,alpha=0.4,figsize=(15,8),cmap='Pastel2')
plt.show()
[scatterd] >INFO> Create scatterplot
Del grafico podemos analizar en base a la orientacion de la flecha y la correlacion asociada para cada feature, que caracteristicas tiene cada cluster.
Se puede desprender que el cluster 0 tiene canciones con "energy" mas alta que el cluster 1, asi como tambien danceability y valence. Del otro lado, tenemos que el cluster 1 tiene mas instrumentales y acousticness en comparacion al cluster 0.
data_scaled['cluster'] = kmeans_model.labels_
data_melt = data_scaled.melt(id_vars=['cluster'])
plt.rcdefaults()
# Create a figure with subplots
fig, axes = plt.subplots(1,len(data_melt['cluster'].unique()), figsize=(12, 5))
# Enumerate through unique cluster values
for i, col in enumerate(data_melt['cluster'].unique()):
# Select the axis for the current subplot
ax = axes[i]
# Filter the data for the current cluster
subset_data = data_melt[data_melt['cluster'] == col]
# Create a point plot on the current axis
sns.boxplot(data=subset_data, x='variable', y='value', ax=ax,showfliers=False)
# Customize the x-axis labels (rotation, alignment)
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right',fontsize=10)
# Add a title to the subplot
ax.set_title(f'Cluster {col}')
ax.set_xlabel('')
# Adjust spacing between subplots
plt.tight_layout()
# Show all subplots together
plt.show()
#Volvemos nuestros datos al estado original para evaluar en base a las documentacion de spotify
df_detail = pd.DataFrame(scaler.inverse_transform(data_scaled.drop(columns='cluster')),columns=data_scaled.drop(columns='cluster').columns,index=data_scaled.index)
df_detail['cluster'] = kmeans_model.labels_
df_detail.groupby('cluster').mean().reset_index()
| cluster | acousticness | danceability | energy | instrumentalness | liveness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0.245993 | 0.559010 | 0.706121 | 0.082332 | 0.264044 | 0.072672 | 0.558234 | 120.836740 | 5.563073 | 0.662844 | 3.951261 | 239157.172592 |
| 1 | 1 | 0.885446 | 0.303837 | 0.162496 | 0.537127 | 0.150072 | 0.043350 | 0.180506 | 102.763305 | 5.049426 | 0.651368 | 3.762577 | 332163.961165 |
Clúster 0: Las canciones en este clúster tienen una "acousticness" relativamente baja, lo que significa que es poco probable que tengan elementos acústicos, pero si mas electrónicos. La "danceability" es razonablemente alta, lo que sugiere que son adecuadas para bailar. Tienen una alta "energy", lo que indica un alto nivel de energía y ritmo. La "instrumentalness" es baja, lo que sugiere que estas canciones probablemente contienen voces. La "liveness" es moderada-baja, lo que indica que la mayoria de las canciones son grabaciones de estudio en lugar de actuaciones en vivo. La "speechiness" es baja, lo que apunta que probablemente es muscia y no un talk show, audio book, etc. La "valence" es relativamente alta, lo que sugiere un tono alegre. El "tempo" es rápido, con un valor de 121.73 BPM. Las canciones en este clúster están en modo mayor. La "duration_ms" es moderada en términos de duración.
Clúster 1: En contraste, las canciones en este clúster tienen una alta "acousticness", lo que sugiere que son predominantemente acústicas. La "danceability" es mas baja, lo que indica que no son muy adecuadas para el baile. Tienen una baja "energy", lo que sugiere un nivel de energía y ritmo más bajo. La "instrumentalness" es alta, lo que sugiere que estas canciones tienen menos probabilidad de contener voces. La "liveness" es baja, lo que sugiere que son grabaciones de estudio. La "speechiness" es baja, lo que indica que probablemente es muscia y no un talk show, audio book, etc. La "valence" es mas baja, lo que sugiere un tono un poco mas melancolico. El "tempo" es más lento, con un valor de 103.32 BPM. Estas canciones están en modo mayor aunque levemente mas bajo que el cluster 0. La "duration_ms" es más larga en comparación con el clúster 0. Finalmente su time_signature tiene una variación importante si se le compara con el otro cluster.
En resumen, el clúster 0 parece estar compuesto por canciones más enérgicas y adecuadas para bailar, mientras que el clúster 1 está formado por canciones predominantemente acústicas con un ritmo más lento y una energía más relajada, pero con un tono mas melancolico. Ambos clústeres tienen características similares en términos de key, mode y speechiness, pero difieren en las demás métricas.
Escoga una canción de la lista, a partir de la cual le gustaría hacer una playlist.
Vea el cluster asociado a esa canción, y seleccione otras canciones del mismo clusters, para crear una playlist de ~3 horas de duración. Para esto último, utilice el campo duration_ms que indica la duración de la canción.
Escriba la lista de canciones seleccionadas, indicando su título y artista/grupo.
Requisito: Indique la canción seleccionada, así como la lista generada. Agregue después lo códigos utilizados para esto.
Respuesta:
La cancion seleccionada corresponde a Papercut de Linkin Park. Esta cancion pertenece al cluster 0.
La playlist dura aproximadamente 3 horas y se encuentra disponible en el siguiente link:
https://open.spotify.com/playlist/74NwjpUzI8VXxi9jsnWVWH
Canciones:
| artist_name | track_name |
|---|---|
| Los Tres Ases | Ofrenda |
| Los Autenticos Decadentes | El Dinero No Es Todo |
| Los Jaivas | Amores De Antes |
| Sr71 | Right Now |
| Andres Calamaro | La Verdadera Libertad |
| Ben Harper | Like A King |
| The Offspring | All Along |
| Quilapayun | El Sabioloco |
| Limp Bizkit | Full Nelson |
| Alejandro Sanz | Dale Al Aire Con Juan Habichuela Y Ketama |
| Helloween | Escalation 666 |
| Deep Purple | Lucille Live 1972 |
| Therion | Flesh Of The Gods |
| Pyotr Ilyich Tchaikovsky | The Nutcracker Op 71 C Danse Russe Trepak Temp... |
| Ricky Martin | She Bangs English Version |
| Los Miserables | Quien Mato A Marilyn |
| Caetano Veloso | Zumbi |
| Diego Torres | La Ultima Noche |
| Caetano Veloso | Meu Rio |
| Franco De Vita | Te Amo En Vivo |
| Miguel Bose | Dulce Pesadilla |
| Helloween | I Live For Your Pain |
| Divididos | El Arriero Live |
| Los Terricolas | Te Juro Que Te Amo |
| Deftones | Teenager |
| Nightwish | Dead Boys Poem |
| Quilapayun | Tio Caiman |
| La Oreja De Van Gogh | Pop |
| Linkin Park | Cure For The Itch |
| Limp Bizkit | My Way |
| Acdc | Give It Up |
| U2 | When I Look At The World |
| Tiesto | Airtight |
| Millencolin | Hellman |
| Sergio Mendes | Going Out Of My Head |
| Los Terricolas | Dos Cosas |
| Outkast | Stankonia Stanklove Feat Big Rube Sleepy Brown |
| Gondwana | Libros Sagrados |
| Limp Bizkit | Boiler |
| Alejandro Sanz | El Alma Al Aire Extended Remix |
| The Offspring | Special Delivery |
| Luis Fonsi | Mi Sueno |
| Tommy Guerrero | Soul Miner |
| Andres Calamaro | Papa Say |
df = pd.concat([data_info,df_detail],axis=1).reset_index(drop=True)
df.head(5)
| artist_name | album_name | track_name | track_id | acousticness | danceability | energy | instrumentalness | liveness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2Pac | The Rose That Grew From Concrete | A River That Flows Forever | 0FvMwjhIU2xsQUj6jKZWrU | 0.608 | 0.506 | 0.413 | 0.000000 | 0.2140 | 0.2630 | 0.581 | 98.720 | 6.0 | 1.0 | 4.0 | 140373.0 | 0 |
| 1 | 2Pac | The Rose That Grew From Concrete | Can U C The Pride In The Panther Album Versio... | 1tUtkxKxeDlyV8y5BPusyI | 0.126 | 0.603 | 0.733 | 0.000008 | 0.0766 | 0.4270 | 0.489 | 82.902 | 8.0 | 1.0 | 4.0 | 176866.0 | 0 |
| 2 | 2Pac | The Rose That Grew From Concrete | Family Tree | 1kxnbvzdnFW2uleKZx6gIC | 0.433 | 0.758 | 0.542 | 0.000000 | 0.0857 | 0.0502 | 0.591 | 93.377 | 1.0 | 0.0 | 4.0 | 199800.0 | 0 |
| 3 | 2Pac | The Rose That Grew From Concrete | God | 1NzCGSToYZti9uKubFHBxx | 0.938 | 0.616 | 0.123 | 0.080600 | 0.1160 | 0.2250 | 0.433 | 76.863 | 6.0 | 0.0 | 3.0 | 47866.0 | 1 |
| 4 | 2Pac | The Rose That Grew From Concrete | If There Be Pain | 0GmCjP5g3yMqqBWhDGjw87 | 0.181 | 0.614 | 0.559 | 0.000000 | 0.1630 | 0.0413 | 0.631 | 81.456 | 9.0 | 1.0 | 4.0 | 271693.0 | 0 |
# Uno de mis grupos favoritos es Linkin Park. Por lo que seleccionare el cluster de esta banda para compararlos con otras que sean del mismo.
df[df.artist_name=='Linkin Park'].head()
| artist_name | album_name | track_name | track_id | acousticness | danceability | energy | instrumentalness | liveness | speechiness | valence | tempo | key | mode | time_signature | duration_ms | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1432 | Linkin Park | Hybrid Theory (Int'l Only DMD w/ Altered iLiner) | A Place For My Head | 4vsIwS56G2Khi36iLoIZgH | 0.0144 | 0.603 | 0.908 | 0.000000 | 0.671 | 0.1840 | 0.457 | 133.063 | 11.0 | 1.0 | 4.0 | 184640.0 | 0 |
| 1433 | Linkin Park | Hybrid Theory (Int'l Only DMD w/ Altered iLiner) | By Myself | 6CqMnBhJkUNzZqkDq2Dz3u | 0.0171 | 0.544 | 0.935 | 0.003010 | 0.297 | 0.1730 | 0.339 | 103.026 | 6.0 | 0.0 | 4.0 | 189800.0 | 0 |
| 1434 | Linkin Park | Hybrid Theory (Int'l Only DMD w/ Altered iLiner) | Crawling | 0EorNL6AhJkrR2h4IltoaQ | 0.0466 | 0.580 | 0.702 | 0.000003 | 0.536 | 0.0337 | 0.299 | 105.076 | 4.0 | 1.0 | 4.0 | 208960.0 | 0 |
| 1435 | Linkin Park | Hybrid Theory (Int'l Only DMD w/ Altered iLiner) | Cure For The Itch | 27P3adXGQqiHjq0Y0C2fS7 | 0.1630 | 0.750 | 0.677 | 0.050900 | 0.247 | 0.2210 | 0.851 | 99.990 | 8.0 | 1.0 | 4.0 | 157200.0 | 0 |
| 1436 | Linkin Park | Hybrid Theory (Int'l Only DMD w/ Altered iLiner) | Forgotten | 6Y7F8eKKHWC7no0Jn0pX1I | 0.0132 | 0.615 | 0.947 | 0.000000 | 0.366 | 0.1100 | 0.498 | 108.193 | 8.0 | 0.0 | 4.0 | 194426.0 | 0 |
#Seleccionamos la cancion Papercut y extraemos el cluster
cluster = df[(df.artist_name=='Linkin Park')&(df.track_name=='Papercut')].iloc[0,-1]
cluster
0
#Artistas dentro del mismo cluster
df_cluster = df[df.cluster==cluster]
df_cluster.artist_name.unique()
array(['2Pac', 'Acdc', 'Alejandro Sanz', 'Alex Bueno', 'Andres Calamaro',
'Aterciopelados', 'Attaque 77', 'Babasonicos', 'Backstreet Boys',
'Belle Sebastian', 'Ben Harper', 'Bersuit Vergarabat', 'Bjork',
'Black Eyed Peas', 'Bon Jovi', 'Caetano Veloso',
'Camille Saintsaens', 'Chancho En Piedra', 'Chayanne',
'Children Of Bodom', 'Coldplay', 'Collective Soul',
'Daniela Mercury', 'David Arkenstone', 'Deep Purple', 'Deftones',
'Diego Torres', 'Divididos', 'Elton John', 'Eminem', 'Erasure',
'Eric Clapton', 'Fatboy Slim', 'Fito Paez', 'Fleetwood Mac',
'Franco De Vita', 'Gilberto Gil', 'Gondwana', 'Green Day',
'Hans Zimmer', 'Helloween', 'Iggy Pop', 'In Flames',
'Intiillimani', 'Ismael Serrano', 'Joan Manuel Serrat',
'John Coltrane', 'Jose Luis Perales', 'Juanes', 'Julieta Venegas',
'Kamelot', 'Kevin Johansen', 'La Mosca Tsetse',
'La Oreja De Van Gogh', 'La Renga', 'Limp Bizkit', 'Linkin Park',
'Los Angeles Azules', 'Los Autenticos Decadentes',
'Los Enanitos Verdes', 'Los Jaivas', 'Los Miserables',
'Los Pericos', 'Los Piojos', 'Los Terricolas', 'Los Tres Ases',
'Lucybell', 'Luis Fonsi', 'Luis Miguel', 'Lynyrd Skynyrd',
'Marco Antonio Solis', 'Matchbox Twenty', 'Matt Uelmen',
'Miguel Bose', 'Millencolin', 'Moby', 'Nacao Zumbi',
'Nana Mouskouri', 'Neil Young', 'Nightwish', 'Oasis', 'Outkast',
'Pantera', 'Papa Roach', 'Paul Mccartney', 'Pearl Jam',
'Pyotr Ilyich Tchaikovsky', 'Queens Of The Stone Age',
'Quilapayun', 'Radiohead', 'Rage Against The Machine',
'Reo Speedwagon', 'Rhapsody', 'Ricardo Arjona', 'Ricky Martin',
'Sergei Rachmaninoff', 'Sergio Mendes', 'Shakira', 'Sr71',
'Sui Generis', 'Sum 41', 'The Avalanches', 'The Black Keys',
'The Cure', 'The Hives', 'The Offspring', 'The White Stripes',
'Therion', 'Thievery Corporation', 'Tiesto', 'Tommy Guerrero',
'Tracy Chapman', 'U2', 'Vicente Fernandez', 'Victor Manuelle',
'Wisin Yandel', 'Wyclef Jean'], dtype=object)
#Creando playlist de 3 horas
playlist = pd.DataFrame(columns=df_cluster.columns)
while True:
if playlist.duration_ms.sum() >= 1.08e+7: #3 horas en milisegundos
break
else:
song = df_cluster.sample(1)
if song.track_id.item() not in playlist.track_id.values:
playlist = pd.concat([playlist, song])
else:
continue
#Nº Canciones en la playlist
print(f'Canciones totales en la playlist: {playlist.shape[0]}')
#Duracion en horas
print(f'Duración en horas: {playlist.duration_ms.sum()/1000/60/60:.2f}')
Canciones totales en la playlist: 44 Duración en horas: 3.03
playlist[['artist_name','track_name']]
| artist_name | track_name | |
|---|---|---|
| 1624 | Los Tres Ases | Ofrenda |
| 1461 | Los Autenticos Decadentes | El Dinero No Es Todo |
| 1498 | Los Jaivas | Amores De Antes |
| 2610 | Sr71 | Right Now |
| 108 | Andres Calamaro | La Verdadera Libertad |
| 463 | Ben Harper | Like A King |
| 2691 | The Offspring | All Along |
| 2259 | Quilapayun | El Sabioloco |
| 1420 | Limp Bizkit | Full Nelson |
| 36 | Alejandro Sanz | Dale Al Aire Con Juan Habichuela Y Ketama |
| 1065 | Helloween | Escalation 666 |
| 749 | Deep Purple | Lucille Live 1972 |
| 2718 | Therion | Flesh Of The Gods |
| 2188 | Pyotr Ilyich Tchaikovsky | The Nutcracker Op 71 C Danse Russe Trepak Temp... |
| 2371 | Ricky Martin | She Bangs English Version |
| 1528 | Los Miserables | Quien Mato A Marilyn |
| 563 | Caetano Veloso | Zumbi |
| 770 | Diego Torres | La Ultima Noche |
| 548 | Caetano Veloso | Meu Rio |
| 938 | Franco De Vita | Te Amo En Vivo |
| 1736 | Miguel Bose | Dulce Pesadilla |
| 1068 | Helloween | I Live For Your Pain |
| 793 | Divididos | El Arriero Live |
| 1594 | Los Terricolas | Te Juro Que Te Amo |
| 765 | Deftones | Teenager |
| 1860 | Nightwish | Dead Boys Poem |
| 2290 | Quilapayun | Tio Caiman |
| 1404 | La Oreja De Van Gogh | Pop |
| 1435 | Linkin Park | Cure For The Itch |
| 1427 | Limp Bizkit | My Way |
| 25 | Acdc | Give It Up |
| 2799 | U2 | When I Look At The World |
| 2741 | Tiesto | Airtight |
| 1766 | Millencolin | Hellman |
| 2578 | Sergio Mendes | Going Out Of My Head |
| 1577 | Los Terricolas | Dos Cosas |
| 1909 | Outkast | Stankonia Stanklove Feat Big Rube Sleepy Brown |
| 977 | Gondwana | Libros Sagrados |
| 1419 | Limp Bizkit | Boiler |
| 38 | Alejandro Sanz | El Alma Al Aire Extended Remix |
| 2700 | The Offspring | Special Delivery |
| 1652 | Luis Fonsi | Mi Sueno |
| 2777 | Tommy Guerrero | Soul Miner |
| 137 | Andres Calamaro | Papa Say |
Spotify a través de su paquete spotipy permite conectarse a la API de Spotify para hacer busquedas, información, o guardar la lista que creó. Como bonus de su nota (+0.5 puntos) transforme su lista encontrada en una playlist de Spotify, y compartala para el resto del curso.
Para mas información de la API, ver
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
#Conectandose con spotify
from spotify_keys import cid, secret
cid = cid
secret = secret
client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
#Top 20 canciones de polyphia
results = sp.search(q='polyphia', limit=20)
for idx, track in enumerate(results['tracks']['items']):
print(idx, track['name'])
0 Playing God 1 G.O.A.T. 2 Ego Death (feat. Steve Vai) 3 ABC (feat. Sophia Black) 4 40oz 5 Champagne 6 Ego Death (feat. Steve Vai) 7 O.D. 8 The Worst 9 Neurotica 10 Playing God 11 Reverie 12 Chimera (feat. Lil West) 13 Euphoria 14 Goose 15 Bloodbath (feat. Chino Moreno) 16 ABC (feat. Sophia Black) 17 So Strange (feat. Cuco) 18 All Falls Apart 19 Genesis (feat. Brasstracks)
#Obtenemos sus features
features_lst=[]
for idx, track in enumerate(results['tracks']['items']):
trackID = track['uri']
features = sp.audio_features(trackID)[0]
features['artist_name'] = track['artists'][0]['name']
features['track_name'] = track['name']
features_lst.append(features)
spotify_df = pd.DataFrame(features_lst)
#Recordemos que dropeamos la variables loudness
col_features = ['acousticness',
'danceability',
'energy',
'instrumentalness',
'liveness',
'speechiness',
'valence',
'tempo',
'key',
'mode',
'time_signature',
'duration_ms']
#Escalamos los datos antes de entregarlos al modelo
spotify_scaled = pd.DataFrame(scaler.fit_transform(spotify_df[col_features]),columns=col_features)
#Prediciendo el cluster para cada cancion
predicted_values = kmeans_model.predict(spotify_scaled)
#juntamos todo
pd.concat([spotify_df, pd.Series(predicted_values,name='cluster')], axis=1)
| danceability | energy | key | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | ... | type | id | uri | track_href | analysis_url | duration_ms | time_signature | artist_name | track_name | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.601 | 0.789 | 4 | -6.822 | 0 | 0.0328 | 0.039500 | 0.81200 | 0.1140 | 0.315 | ... | audio_features | 3nBGFgfRQ8ujSmu5cGlZIU | spotify:track:3nBGFgfRQ8ujSmu5cGlZIU | https://api.spotify.com/v1/tracks/3nBGFgfRQ8uj... | https://api.spotify.com/v1/audio-analysis/3nBG... | 205926 | 4 | Polyphia | Playing God | 1 |
| 1 | 0.660 | 0.760 | 11 | -5.488 | 0 | 0.0332 | 0.000601 | 0.31200 | 0.0949 | 0.359 | ... | audio_features | 0YPuRrM2NwzdtuShUKkts6 | spotify:track:0YPuRrM2NwzdtuShUKkts6 | https://api.spotify.com/v1/tracks/0YPuRrM2Nwzd... | https://api.spotify.com/v1/audio-analysis/0YPu... | 215999 | 4 | Polyphia | G.O.A.T. | 0 |
| 2 | 0.655 | 0.763 | 2 | -5.202 | 0 | 0.0526 | 0.037400 | 0.67800 | 0.0703 | 0.445 | ... | audio_features | 0Qj7PB41b6XgkApKPwDy1r | spotify:track:0Qj7PB41b6XgkApKPwDy1r | https://api.spotify.com/v1/tracks/0Qj7PB41b6Xg... | https://api.spotify.com/v1/audio-analysis/0Qj7... | 350086 | 4 | Polyphia | Ego Death (feat. Steve Vai) | 1 |
| 3 | 0.681 | 0.827 | 6 | -4.051 | 0 | 0.0651 | 0.055300 | 0.00000 | 0.2990 | 0.750 | ... | audio_features | 7D4lgMbSs10AQ0zX3ZldJc | spotify:track:7D4lgMbSs10AQ0zX3ZldJc | https://api.spotify.com/v1/tracks/7D4lgMbSs10A... | https://api.spotify.com/v1/audio-analysis/7D4l... | 152361 | 4 | Polyphia | ABC (feat. Sophia Black) | 0 |
| 4 | 0.597 | 0.845 | 1 | -7.803 | 0 | 0.0335 | 0.002670 | 0.17300 | 0.2030 | 0.630 | ... | audio_features | 3v3VFa7Dt32gNR27jfw7DG | spotify:track:3v3VFa7Dt32gNR27jfw7DG | https://api.spotify.com/v1/tracks/3v3VFa7Dt32g... | https://api.spotify.com/v1/audio-analysis/3v3V... | 233819 | 4 | Polyphia | 40oz | 0 |
| 5 | 0.461 | 0.962 | 8 | -4.665 | 0 | 0.1200 | 0.000013 | 0.91500 | 0.3590 | 0.608 | ... | audio_features | 6ZDHFz6h8d93EPAIq5hDuE | spotify:track:6ZDHFz6h8d93EPAIq5hDuE | https://api.spotify.com/v1/tracks/6ZDHFz6h8d93... | https://api.spotify.com/v1/audio-analysis/6ZDH... | 265385 | 4 | Polyphia | Champagne | 0 |
| 6 | 0.655 | 0.763 | 2 | -5.202 | 0 | 0.0526 | 0.037400 | 0.67800 | 0.0703 | 0.445 | ... | audio_features | 7xyvWSVkxaoLdSuEdgdhBe | spotify:track:7xyvWSVkxaoLdSuEdgdhBe | https://api.spotify.com/v1/tracks/7xyvWSVkxaoL... | https://api.spotify.com/v1/audio-analysis/7xyv... | 350086 | 4 | Polyphia | Ego Death (feat. Steve Vai) | 1 |
| 7 | 0.633 | 0.851 | 11 | -5.724 | 0 | 0.0685 | 0.000569 | 0.89800 | 0.1400 | 0.592 | ... | audio_features | 1U2yqd3Bp4vBuIQ3NGfS6C | spotify:track:1U2yqd3Bp4vBuIQ3NGfS6C | https://api.spotify.com/v1/tracks/1U2yqd3Bp4vB... | https://api.spotify.com/v1/audio-analysis/1U2y... | 202163 | 4 | Polyphia | O.D. | 0 |
| 8 | 0.656 | 0.696 | 4 | -5.935 | 0 | 0.0438 | 0.118000 | 0.45000 | 0.1610 | 0.400 | ... | audio_features | 5l3WNlq4rag0fFP0TI9qeJ | spotify:track:5l3WNlq4rag0fFP0TI9qeJ | https://api.spotify.com/v1/tracks/5l3WNlq4rag0... | https://api.spotify.com/v1/audio-analysis/5l3W... | 245779 | 4 | Polyphia | The Worst | 1 |
| 9 | 0.689 | 0.679 | 11 | -5.239 | 0 | 0.0543 | 0.022300 | 0.71000 | 0.1030 | 0.922 | ... | audio_features | 0uzwUl56ZPCJtRlqhG5OFo | spotify:track:0uzwUl56ZPCJtRlqhG5OFo | https://api.spotify.com/v1/tracks/0uzwUl56ZPCJ... | https://api.spotify.com/v1/audio-analysis/0uzw... | 194590 | 4 | Polyphia | Neurotica | 0 |
| 10 | 0.601 | 0.789 | 4 | -6.822 | 0 | 0.0328 | 0.039500 | 0.81200 | 0.1140 | 0.315 | ... | audio_features | 6AhwAWzSlISc5ZvGonkgdN | spotify:track:6AhwAWzSlISc5ZvGonkgdN | https://api.spotify.com/v1/tracks/6AhwAWzSlISc... | https://api.spotify.com/v1/audio-analysis/6Ahw... | 205926 | 4 | Polyphia | Playing God | 1 |
| 11 | 0.553 | 0.824 | 4 | -5.261 | 0 | 0.0550 | 0.001850 | 0.82000 | 0.3050 | 0.608 | ... | audio_features | 1W5zSvAeIXlw6odInPE4m5 | spotify:track:1W5zSvAeIXlw6odInPE4m5 | https://api.spotify.com/v1/tracks/1W5zSvAeIXlw... | https://api.spotify.com/v1/audio-analysis/1W5z... | 242694 | 4 | Polyphia | Reverie | 0 |
| 12 | 0.664 | 0.835 | 8 | -4.858 | 1 | 0.0442 | 0.035900 | 0.12800 | 0.1850 | 0.485 | ... | audio_features | 5aVKIdM550lRzk7rFbPcF7 | spotify:track:5aVKIdM550lRzk7rFbPcF7 | https://api.spotify.com/v1/tracks/5aVKIdM550lR... | https://api.spotify.com/v1/audio-analysis/5aVK... | 236653 | 4 | Polyphia | Chimera (feat. Lil West) | 0 |
| 13 | 0.421 | 0.931 | 0 | -4.569 | 1 | 0.0397 | 0.000451 | 0.28700 | 0.0774 | 0.393 | ... | audio_features | 0Jf3QiP8WrXEoFsaUsJi0b | spotify:track:0Jf3QiP8WrXEoFsaUsJi0b | https://api.spotify.com/v1/tracks/0Jf3QiP8WrXE... | https://api.spotify.com/v1/audio-analysis/0Jf3... | 256333 | 4 | Polyphia | Euphoria | 0 |
| 14 | 0.640 | 0.846 | 9 | -5.201 | 0 | 0.0298 | 0.021800 | 0.54100 | 0.1160 | 0.508 | ... | audio_features | 2v7iJcMoQcN40fK9XEb42q | spotify:track:2v7iJcMoQcN40fK9XEb42q | https://api.spotify.com/v1/tracks/2v7iJcMoQcN4... | https://api.spotify.com/v1/audio-analysis/2v7i... | 255455 | 4 | Polyphia | Goose | 0 |
| 15 | 0.600 | 0.817 | 4 | -4.636 | 0 | 0.0488 | 0.000794 | 0.00321 | 0.4620 | 0.322 | ... | audio_features | 2IMHE3XJcsqTIbSGOIY6Jy | spotify:track:2IMHE3XJcsqTIbSGOIY6Jy | https://api.spotify.com/v1/tracks/2IMHE3XJcsqT... | https://api.spotify.com/v1/audio-analysis/2IMH... | 230951 | 4 | Polyphia | Bloodbath (feat. Chino Moreno) | 0 |
| 16 | 0.681 | 0.827 | 6 | -4.051 | 0 | 0.0651 | 0.055300 | 0.00000 | 0.2990 | 0.750 | ... | audio_features | 4c8TMfsKJIrRgCWs8LCEbQ | spotify:track:4c8TMfsKJIrRgCWs8LCEbQ | https://api.spotify.com/v1/tracks/4c8TMfsKJIrR... | https://api.spotify.com/v1/audio-analysis/4c8T... | 152361 | 4 | Polyphia | ABC (feat. Sophia Black) | 0 |
| 17 | 0.553 | 0.885 | 0 | -4.063 | 1 | 0.0385 | 0.000667 | 0.00072 | 0.2090 | 0.484 | ... | audio_features | 6MYzjR2rH0hfz91FsaR1ox | spotify:track:6MYzjR2rH0hfz91FsaR1ox | https://api.spotify.com/v1/tracks/6MYzjR2rH0hf... | https://api.spotify.com/v1/audio-analysis/6MYz... | 240006 | 4 | Polyphia | So Strange (feat. Cuco) | 0 |
| 18 | 0.364 | 0.423 | 2 | -8.187 | 1 | 0.0343 | 0.001080 | 0.97500 | 0.6970 | 0.187 | ... | audio_features | 4iDH45ZVIdHzDhLYd1FyKF | spotify:track:4iDH45ZVIdHzDhLYd1FyKF | https://api.spotify.com/v1/tracks/4iDH45ZVIdHz... | https://api.spotify.com/v1/audio-analysis/4iDH... | 79688 | 3 | Polyphia | All Falls Apart | 1 |
| 19 | 0.674 | 0.712 | 0 | -5.338 | 1 | 0.0888 | 0.093500 | 0.84700 | 0.1650 | 0.575 | ... | audio_features | 71N78jUmfhLeRDlLd8elfl | spotify:track:71N78jUmfhLeRDlLd8elfl | https://api.spotify.com/v1/tracks/71N78jUmfhLe... | https://api.spotify.com/v1/audio-analysis/71N7... | 194632 | 4 | Polyphia | Genesis (feat. Brasstracks) | 1 |
20 rows × 21 columns
from spotipy.oauth2 import SpotifyOAuth
scope = "playlist-modify-public"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope, client_id=cid, client_secret=secret, redirect_uri='http://localhost:3000'))
# sp.me()
user = sp.me()['id']
spotify_playlist = sp.user_playlist_create(user=user,
name='DAD_Playlist')
spotify_playlist_id = spotify_playlist['id']
## ID del playlist creado ubicado en uri
newPlaylistId = spotify_playlist_id
## Extraer los track_id de la lista de canciones seleccionadas
trackIDs = list(playlist.track_id)
## Agregar la lista de canciones al playlist de Spotify
sp.user_playlist_add_tracks(user, newPlaylistId, trackIDs)
{'snapshot_id': 'Miw2ZjM5MjQyNmJhMTk1MDczMDVjMmJhNmU1ZWU2NjJiMWE4YTliNDgz'}
print(f"Lista disponible en: {spotify_playlist['external_urls']['spotify']}")
Lista disponible en: https://open.spotify.com/playlist/74NwjpUzI8VXxi9jsnWVWH